package org.infodavid.common.impl.persistence;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.ColumnResult;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.PersistenceException;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.SqlResultSetMappings;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.infodavid.common.annotations.AImplementation;
import org.infodavid.common.annotations.AImplementation.ERuntime;
import org.infodavid.common.dto.IUserListItem;
import org.infodavid.common.dto.IUserGroupListItem.EUserRole;
import org.infodavid.common.ejb.UserBean;
import org.infodavid.common.impl.services.ServiceUtil;
import org.infodavid.common.model.IGenericPropertiesMap;
import org.infodavid.common.model.IGenericProperty;
import org.infodavid.common.model.IUser;
import org.infodavid.common.model.IUserGroup;
import org.infodavid.common.model.components.IEMail;
import org.infodavid.common.model.criteria.IExtendedSearchCriteria;
import org.infodavid.common.model.criteria.IGenericPropertyCriteria;
import org.infodavid.common.model.criteria.IUserCriteria;
import org.infodavid.common.persistence.IQueryParameter;
import org.infodavid.common.persistence.PersistenceSession;
import org.infodavid.common.persistence.QueryParameterFactory;
import org.infodavid.common.persistence.exceptions.NoDataFoundException;
import org.infodavid.common.persistence.impl.DataServiceHelper;
import org.infodavid.common.persistence.impl.PersistenceUtil;
import org.infodavid.common.services.exceptions.ServiceException;
import org.joda.time.DateTime;

import com.wybness.common.impl.persistence.AbstractUserDataService;
import com.wybness.common.persistence.PersonDataService;
import com.wybness.common.persistence.UserDataService;

/**
 * The Class UserDataServiceImpl.
 */
@NamedQueries(value = {
    @NamedQuery(name = UserDataService.QUERY_FIND_COUNT, query = "select count(d." + IUser.KEY
        + ") from IUser d"),
    @NamedQuery(name = UserDataService.QUERY_FIND, query = "select d from IUser d"),
    @NamedQuery(name = UserDataService.QUERY_FIND_ACTIVE_COUNT, query = "select count(d."
        + IUser.KEY + ") from IUser d where d.archived = 0"),
    @NamedQuery(name = UserDataService.QUERY_FIND_ACTIVE, query = "select d from IUser d where d.archived = 0"),
    @NamedQuery(name = UserDataService.QUERY_FIND_ACTIVE_BY_NAME, query = "select d from IUser d where d.archived = 0 and :"
        + IUser.NAME
        + " is not null and d."
        + IUser.NAME
        + " = :"
        + IUser.NAME
        + " and d.archived = 0"),
    @NamedQuery(name = UserDataService.QUERY_FIND_ACTIVE_BY_EMAIL, query = "select d from IUser d where :"
        + IUser.EMAIL + " is not null and d.email.value = :" + IUser.EMAIL + " and d.archived = 0"),
    @NamedQuery(name = UserDataService.QUERY_FIND_BY_CREATION_DATE, query = "select d from IUser d where d.archived = 0 and d."
        + IUser.CREATION_DATE + " between :date1 and :date2"),
    @NamedQuery(name = UserDataService.QUERY_FIND_BY_CREATION_DATE_COUNT, query = "select count(d."
        + IUser.KEY + ") from IUser d where d.archived = 0 and d." + IUser.CREATION_DATE
        + " between :date1 and :date2"),
    @NamedQuery(name = UserDataService.QUERY_FIND_BY_MODIFICATION_DATE, query = "select d from IUser d where d.archived = 0 and d."
        + IUser.MODIFICATION_DATE + " between :date1 and :date2"),
    @NamedQuery(name = UserDataService.QUERY_FIND_BY_MODIFICATION_DATE_COUNT, query = "select count(d."
        + IUser.KEY
        + ") from IUser d where d.archived = 0 and d."
        + IUser.MODIFICATION_DATE
        + " between :date1 and :date2"),
    @NamedQuery(name = UserDataService.QUERY_FIND_ACTIVE_BY_ROLE, query = "select distinct d from IUser d join d.groups g where d.archived = 0 and g.role = :"
        + IUserGroup.ROLE),
    @NamedQuery(name = UserDataService.QUERY_FIND_ACTIVE_BY_ROLE_COUNT, query = "select count(distinct d.key) from IUser d join d.groups g where d.archived = 0 and g.role = :"
        + IUserGroup.ROLE),
    @NamedQuery(name = UserDataService.QUERY_FIND_BY_GROUP, query = "select distinct d from IUser d join d.groups g where d.archived = 0 and g.key = :"
        + IUserGroup.ID),
    @NamedQuery(name = UserDataService.QUERY_FIND_BY_GROUP_COUNT, query = "select count(distinct d.key) from IUser d join d.groups g where d.archived = 0 and g.key = :"
        + IUserGroup.ID),
    @NamedQuery(name = UserDataService.QUERY_FIND_PROPERTIES, query = "select elements(p) from IUser a"
        + " join a.genericData p where (:"
        + IUser.ID
        + " is null or a."
        + IUser.KEY
        + " = :"
        + IUser.ID
        + ") and (:"
        + IGenericProperty.SCOPE
        + " is null or p."
        + IGenericProperty.SCOPE
        + " like :"
        + IGenericProperty.SCOPE
        + ") and (:"
        + IGenericProperty.NAME
        + " is null or p."
        + IGenericProperty.NAME
        + " like :"
        + IGenericProperty.NAME + ')')
})
@SqlResultSetMappings({
    @SqlResultSetMapping(name = "IUser.role", columns = @ColumnResult(name = "value")),
    @SqlResultSetMapping(name = "IUser.count", columns = @ColumnResult(name = "value"))
})
@NamedNativeQueries(value = {
  @NamedNativeQuery(name = UserDataService.QUERY_FIND_ROLES, query = "select distinct(g."
      + IUserGroup.ROLE + ") as value from USERGROUPS g join USERGROUPS_USERS gu on gu."
      + IUserGroup.ID + " = g." + IUserGroup.ID + " where g.archived = 0 and gu." + IUser.ID
      + " = :" + IUser.ID, resultSetMapping = "IUser.role")
})
@AImplementation(value = UserDataService.class, runtime = ERuntime.DEFAULT)
public final class UserDataServiceImpl extends AbstractUserDataService {

  /** The Constant log. */
  private static final Log LOGGER = LogFactory
      .getLog(AbstractUserDataService.class);

  /** The helper. */
  private final DataServiceHelper<IUserListItem, IUser, Long> helper;

  /**
   * The Constructor.
   */
  private UserDataServiceImpl() {
    super();

    helper = new DataServiceHelper<IUserListItem, IUser, Long>(this);
  }

  /*
   * (non-Javadoc)
   *
   * @see org.infodavid.common.persistence.IDataService#getEntityClass()
   */
  public Class<? extends IUser> getEntityClass() {
    return UserBean.class;
  }

  /*
   * (non-Javadoc)
   *
   * @see org.infodavid.common.persistence.IDataService#find(org.infodavid.common.
   * persistence.PersistenceSession , java.util.List, java.util.List)
   */
  public long find(final PersistenceSession session,
      final List<IUserListItem> entities,
      final List<IQueryParameter> parameters) throws PersistenceException {
    return helper.findListItems(session, entities, parameters, QUERY_FIND,
        QUERY_FIND_COUNT);
  }

  /*
   * See super class or interface. (non-Javadoc)
   *
   * @see
   * org.infodavid.common.persistence.UserDataService#findByEmail(org.infodavid
   * .common.persistence. PersistenceSession, java.lang.String)
   */
  @Override
  public IUser findByEmail(final PersistenceSession session,
      final String value) throws PersistenceException, ServiceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching by email using session = " + session
          + ", value = " + value);
    }

    if (StringUtils.isEmpty(value)) {
      throw new IllegalArgumentException(IEMail.EMAIL);
    }

    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil
        .borrowQueryParameters(1);

    params.get(0).setName(IUser.EMAIL).setValue(value);

    try {
      return helper.find(session, params, IUser.class,
          QUERY_FIND_ACTIVE_BY_EMAIL);
    } finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * org.infodavid.common.persistence.UserDataService#findByGroup(org.infodavid
   * .common.persistence. PersistenceSession, java.lang.Long, java.util.List,
   * org.infodavid.common.model.criteria.ISearchCriteria)
   */
  @Override
  public long findByGroup(final PersistenceSession session,
      final Long groupKey, final List<IUserListItem> results,
      final IExtendedSearchCriteria criteria) throws ServiceException,
      PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching by group using session = " + session
          + ", groupKey = " + groupKey + ", entities = " + results);
    }

    if (groupKey == null) {
      throw new IllegalArgumentException(IUserGroup.KEY);
    }

    if (groupKey.longValue() <= 0) {
      throw new IllegalArgumentException(IUserGroup.KEY + ':' + groupKey);
    }

    PersistenceUtil.validate(session);
    helper.validate(results);

    final QueryParameterFactory paramFactory = QueryParameterFactory
        .getSingleton();
    final List<IQueryParameter> params = new ArrayList<IQueryParameter>();
    final IQueryParameter parameter = paramFactory.borrow();

    parameter.setName(IUserGroup.ID);
    parameter.setValue(groupKey);
    params.add(parameter);

    PersistenceUtil.applyCriteria(params, criteria);

    try {
      return helper.findListItems(session, results, params,
          QUERY_FIND_BY_GROUP, QUERY_FIND_BY_GROUP_COUNT);
    } finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * org.infodavid.common.persistence.UserDataService#findByRole(org.infodavid
   * .common.persistence. PersistenceSession,
   * org.infodavid.common.model.listitems.IGroupListItem.EGroupRole,
   * java.util.List, org.infodavid.common.model.criteria.ISearchCriteria)
   */
  @Override
  public long findByRole(final PersistenceSession session,
      final EUserRole role, final List<IUser> results,
      final IExtendedSearchCriteria criteria) throws ServiceException,
      PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching by role using session = " + session
          + ", value = " + role + ", entities = " + results);
    }

    PersistenceUtil.validate(session);
    helper.validate(results);

    if (role == null) {
      throw new IllegalArgumentException(IUserGroup.ROLE);
    }

    final QueryParameterFactory paramFactory = QueryParameterFactory
        .getSingleton();
    final List<IQueryParameter> params = new ArrayList<IQueryParameter>();
    final IQueryParameter parameter = paramFactory.borrow();

    parameter.setName(IUserGroup.ROLE);
    parameter.setValue(role);
    params.add(parameter);

    PersistenceUtil.applyCriteria(params, criteria);

    try {
      return helper.find(session, results, params,
          QUERY_FIND_ACTIVE_BY_ROLE, QUERY_FIND_ACTIVE_BY_ROLE_COUNT);
    } finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   *
   * @see
   * org.infodavid.common.persistence.UserDataService#findRoles(org.infodavid.
   * common.persistence. PersistenceSession, java.lang.Long)
   */
  @Override
  @SuppressWarnings("unchecked")
  public List<EUserRole> findRoles(final PersistenceSession session,
      final Long key) throws ServiceException, PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching roles using session = " + session
          + ", value = " + key);
    }

    if (key == null) {
      throw new IllegalArgumentException(IUser.KEY);
    }

    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil
        .borrowQueryParameters(1);
    List<EUserRole> results = null;
    List<String> roles = null;

    params.get(0).setName(IUser.ID).setValue(key);

    try {
      session.flush(); // use of native query

      roles = (List<String>) session.executeQuery(EUserRole.class,
          QUERY_FIND_ROLES, params, false);

      if (!(roles == null || roles.isEmpty())) {
        results = new ArrayList<EUserRole>(roles.size());

        for (final String item : roles) {
          results.add(EUserRole.valueOf(item));
        }
      }
    } finally {
      PersistenceUtil.releaseQueryParameters(params);
    }

    return results;
  }

  /**
   * Find by name.
   */
  @Override
  public IUser findByName(final PersistenceSession session, final String value)
      throws PersistenceException, ServiceException {
    return helper.findByName(session, value, null,
        QUERY_FIND_ACTIVE_BY_NAME);
  }

  /*
   * (non-Javadoc)
   *
   * @see org.infodavid.common.persistence.IDataService#find(org.infodavid.common.
   * persistence.PersistenceSession , java.util.List,
   * org.infodavid.common.model.criteria.ISearchCriteria)
   */
  @SuppressWarnings("unchecked")
  public long find(final PersistenceSession session,
      final List<IUserListItem> entities, final IUserCriteria criteria)
      throws PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching by criteria using session = " + session
          + ", entities = " + entities + ", criteria = " + criteria);
    }

    PersistenceUtil.validate(session);
    helper.validate(entities);

    final List<IQueryParameter> params = new ArrayList<IQueryParameter>();
    final HibernateHelper hbnHelper = HibernateHelper.getInstance();
    final Criteria hbnCriteria = hbnHelper.createCriteria(session,
        UserBean.class);
    final Criteria hbnCountCriteria = hbnHelper.createCriteria(session,
        UserBean.class);

    if (criteria == null) {
      hbnCriteria.add(Restrictions.eq(IUser.ARCHIVED, Boolean.FALSE));
      hbnCountCriteria
          .add(Restrictions.eq(IUser.ARCHIVED, Boolean.FALSE));
    } else {
      final Boolean archived = criteria.isArchived();
      final String name = criteria.getNamePattern();
      final Long key = criteria.getGroupKey();

      if (!(key == null || key.longValue() < 0)) {
        hbnCriteria.createAlias(IUser.GROUPS, "grp").add(
            Restrictions.eq("grp." + IUserGroup.KEY, key));
        hbnCountCriteria.createAlias(IUser.GROUPS, "grp").add(
            Restrictions.eq("grp." + IUserGroup.KEY, key));
      }

      if (StringUtils.isNotEmpty(name)) {
        hbnCriteria.add(Restrictions.like(IUser.NAME, name));
        hbnCountCriteria.add(Restrictions.like(IUser.NAME, name));
      }

      if (archived == null) {
        hbnCriteria.add(Restrictions.eq(IUser.ARCHIVED, Boolean.FALSE));
        hbnCountCriteria.add(Restrictions.eq(IUser.ARCHIVED,
            Boolean.FALSE));
      } else {
        hbnCriteria.add(Restrictions.eq(IUser.ARCHIVED, archived));
        hbnCountCriteria.add(Restrictions.eq(IUser.ARCHIVED, archived));
      }
    }

    PersistenceUtil.applyCriteria(params, criteria);
    hbnHelper.applyParameters(params, hbnCriteria);

    try {
      hbnCountCriteria.setProjection(Projections.count(IUser.KEY));
      session.flush();

      final long count = ((Number) hbnCountCriteria.uniqueResult())
          .longValue();

      if (count > 0) {
        entities.addAll(hbnCriteria.list());
      }

      return count;
    } finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   *
   * @see
   * org.infodavid.common.persistence.IDataService#clear(org.infodavid.common.
   * persistence.PersistenceSession , java.util.List)
   */
  public void clear(final PersistenceSession session,
      final List<IQueryParameter> parameters) throws PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Removing entities using session = " + session
          + ", parameters = " + parameters);
    }

    session.removeEntities(
        session.findEntities(IUser.class, QUERY_FIND, parameters), null);
  }

  /*
   * See super class or interface. (non-Javadoc)
   *
   * @see
   * org.infodavid.common.persistence.IDataService#count(org.infodavid.common.
   * persistence.PersistenceSession , java.util.List)
   */
  public long count(final PersistenceSession session,
      final List<IQueryParameter> parameters) throws PersistenceException {
    return helper.count(session, parameters, QUERY_FIND_COUNT);
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * org.infodavid.common.persistence.IDataService#findByCreationDate(org.infodavid
   * .common.persistence .PersistenceSession, org.joda.time.DateTime,
   * org.joda.time.DateTime, java.util.List, java.util.List)
   */
  public long findByCreationDate(final PersistenceSession session,
      final DateTime date1, final DateTime date2,
      final List<IUserListItem> entities,
      final List<IQueryParameter> parameters) throws PersistenceException {
    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil
        .initializeQueryParameters(parameters);

    try {
      PersistenceUtil.applySorting(params, IUser.CREATION_DATE);

      return helper.findListItemsByDate(session, date1, date2, entities,
          params, QUERY_FIND_BY_CREATION_DATE,
          QUERY_FIND_BY_CREATION_DATE_COUNT);
    } finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   *
   * @see
   * org.infodavid.common.persistence.IDataService#findByModificationDate(com
   * .infodavid.orgmon.persistence .PersistenceSession, org.joda.time.DateTime,
   * org.joda.time.DateTime, java.util.List, java.util.List)
   */
  public long findByModificationDate(final PersistenceSession session,
      final DateTime date1, final DateTime date2,
      final List<IUserListItem> entities,
      final List<IQueryParameter> parameters) throws PersistenceException {
    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil
        .initializeQueryParameters(parameters);

    try {
      PersistenceUtil.applySorting(params, IUser.MODIFICATION_DATE);

      return helper.findListItemsByDate(session, date1, date2, entities,
          params, QUERY_FIND_BY_MODIFICATION_DATE,
          QUERY_FIND_BY_MODIFICATION_DATE_COUNT);
    } finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   *
   * @see
   * org.infodavid.common.persistence.IDataService#update(org.infodavid.common
   * .persistence. PersistenceSession, java.util.Collection)
   */
  public void update(final PersistenceSession session,
      final Collection<IUser> collection) throws PersistenceException {
    final PersonDataService personDataService = PersonDataService
        .getInstance();

    for (final IUser value : collection) {
      if (value.getPerson() != null) {
        personDataService.update(session, value.getPerson());
      }
    }

    session.updateEntities(collection, null);
  }

  /*
   * See super class or interface. (non-Javadoc)
   *
   * @see
   * org.infodavid.common.persistence.IDataService#update(org.infodavid.common
   * .persistence. PersistenceSession, org.infodavid.common.model.IDataObject)
   */
  public void update(final PersistenceSession session, final IUser value)
      throws PersistenceException {
    if (value.getPerson() != null) {
      PersonDataService.getInstance().update(session, value.getPerson());
    }

    session.updateEntity(value, null);
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * org.infodavid.common.persistence.UserDataService#findProperties(org.infodavid
   * .common.persistence .PersistenceSession, java.util.Collection, long,
   * org.infodavid.common.model.criteria.IGenericPropertyCriteria)
   */
  @SuppressWarnings({ "unchecked", "boxing" })
  @Override
  public void findProperties(final PersistenceSession session,
      final Collection<IGenericProperty> results, final long userKey,
      final IGenericPropertyCriteria criteria)
      throws PersistenceException, ServiceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching properties by criteria using session = "
          + session + ", results = " + results + ", criteria = "
          + criteria);
    }

    PersistenceUtil.validate(session);
    ServiceUtil.validate(results);

    final List<IQueryParameter> params = PersistenceUtil
        .borrowQueryParameters(3);

    params.get(0).setName(IUser.ID).setValue(userKey);

    if (criteria == null) {
      params.get(1).setName(IGenericProperty.NAME).setValue(null);
      params.get(2).setName(IGenericProperty.SCOPE).setValue(null);
    } else {
      params.get(1).setName(IGenericProperty.NAME)
          .setValue(criteria.getNamePattern());
      params.get(2).setName(IGenericProperty.SCOPE)
          .setValue(criteria.getScopePattern());
    }

    try {
      session.flush();

      final List<IGenericProperty> properties = (List<IGenericProperty>) session
          .executeQuery(IGenericProperty.class,
              QUERY_FIND_PROPERTIES, params, false);

      if (!(properties == null || properties.isEmpty())) {
        results.addAll(properties);
      }
    } finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * org.infodavid.common.persistence.UserDataService#updateProperties(org.infodavid
   * .common.persistence .PersistenceSession, long, java.util.Collection)
   */
  @SuppressWarnings("boxing")
  @Override
  public DateTime updateProperties(final PersistenceSession session,
      final long userKey, final Collection<IGenericProperty> properties)
      throws PersistenceException, ServiceException {
    final IUser entity = findByPrimaryKey(session, userKey);

    if (entity == null) {
      throw new NoDataFoundException(getEntityClass(), userKey);
    }

    final IGenericPropertiesMap map = entity.getProperties();

    for (final IGenericProperty item : properties) {
      map.addData(item);
    }

    update(session, entity);

    return entity.getModificationDate();
  }
}
